#include "xmppim/xmppclient.h"
#include "xmppim/connectionthread.h"

namespace yoohoo_xmppim {

XmppClient::XmppClient(ConnectionConfig& config, TlsCallback* tlsCallback, QObject* parent) : _config(config),
	_tlsCallback(tlsCallback), _connection(0), _connectionThread(0), _from(0) {
}

XmppClient::~XmppClient() {
	disconnectFromHost();

	if (_tlsCallback) {
		delete _tlsCallback;
	}

	foreach (PresenceListener* listener, _presenceListeners) {
		delete listener;
	}

	foreach (MessageListener* listener, _messageListeners) {
		delete listener;
	}

	foreach (IqListener* listener, _iqListeners) {
		delete listener;
	}

	foreach (StanzaErrorListener* listener, _stanzaErrorListeners) {
		delete listener;
	}

	if (_streamErrorListener) {
		delete _streamErrorListener;
	}
}

const ConnectionConfig& XmppClient::config() const {
	return _config;
}

void XmppClient::setTlsCallback(TlsCallback* tlsCallback) {
	if (_tlsCallback) {
		delete _tlsCallback;
	}
	_tlsCallback = tlsCallback;

	foreach (StreamFeatureFactory* factory, _streamFeatureFactories) {
		delete factory;
	}
}

TlsCallback* XmppClient::tlsCallback() {
	return _tlsCallback;
}

void XmppClient::connectToHost() {
	disconnectFromHost();

	_connection = new Connection(_config);

	connect(_connection, SIGNAL(connectionError(Connection::ConnectionError)), this, SLOT(processConnectionError(Connection::ConnectionError)));
	connect(_connection, SIGNAL(connectionState(Connection::ConnectionState)), this, SLOT(processConnectionState(Connection::ConnectionState)));
	connect(_connection, SIGNAL(streamError(StreamError)), this, SLOT(processStreamError(StreamError)));
	connect(_connection, SIGNAL(streamError(StanzaError)), this, SLOT(processStreamError(StanzaError)));
	connect(_connection, SIGNAL(readyToAuthenticate()), this, SLOT(processReadyToAuthenticate()));
	connect(_connection, SIGNAL(authenticated()), this, SLOT(processAuthenticated()));
	connect(_connection, SIGNAL(authFailure(Connection::AuthFailure)), this, SLOT(processAuthFailure(Connection::AuthFailure)));
	connect(_connection, SIGNAL(resourceBindingError(Connection::ResourceBindingError)),
		this, SLOT(processResourceBindingError(Connection::ResourceBindingError)));
	connect(_connection, SIGNAL(resourceBinded(QString)), this, SLOT(processResourceBinded(QString)));
	connect(_connection, SIGNAL(sessionEstablishmentError(Connection::SessionEstablishmentError)),
		this, SLOT(processSessionEstablishmentError(Connection::SessionEstablishmentError)));
	connect(_connection, SIGNAL(sessionEstablished()), this, SLOT(processSessionEstablished()));
	connect(_connection, SIGNAL(streamClosed()), this, SLOT(processStreamClosed()));
	connect(_connection, SIGNAL(messageReceived(Message)), this, SLOT(processMessageReceived(Message)));
	connect(_connection, SIGNAL(presenceReceived(Presence)), this, SLOT(processPresenceReceived(Presence)));
	connect(_connection, SIGNAL(iqReceived(Iq)), this, SLOT(processIqReceived(Iq)));

	if (_tlsCallback != 0) {
		_connection->setTlsCallback(_tlsCallback);
	}

	foreach (StreamFeatureFactory* streamFeatureFactory, _streamFeatureFactories) {
		_connection->registerStreamFeature(streamFeatureFactory);
	}

	if (!_connectionThread)
		_connectionThread = new ConnectionThread(_connection, this);
	_connectionThread->start();

	_connection->moveToThread(_connectionThread);

	QMetaObject::invokeMethod(_connection, "connectToHost");
}

void XmppClient::disconnectFromHost() {
	if (_connectionThread) {
		_connectionThread->quit();
		_connectionThread->deleteLater();
		_connectionThread = 0;
	}

	_connection->deleteLater();
	_connection = 0;

	if (_from) {
		delete _from;
		_from = 0;
	}
}

void XmppClient::authenticate(QString userName, QString password) {
	if (_connection) {
		QMetaObject::invokeMethod(_connection, "authenticate", Q_ARG(QString, userName), Q_ARG(QString, password));
	}
}

void XmppClient::sendIq(Iq iq) {
	Q_ASSERT(_connection);
	Connection::sendIq(iq, _connection);
}

void XmppClient::sendPresence(Presence presence) {
	Q_ASSERT(_connection);
	Connection::sendPresence(presence, _connection);
}

void XmppClient::sendMessage(Message message) {
	Q_ASSERT(_connection);
	Connection::sendMessage(message, _connection);
}

void XmppClient::sendXml(const QString& xml) {
	Q_ASSERT(_connection);
	Connection::write(xml, _connection);
}

void XmppClient::registerStreamFeature(StreamFeatureFactory* streamFeatureFactory) {
	_streamFeatureFactories.append(streamFeatureFactory);
}

bool XmppClient::isStreamFeatureSupported(QString name, QString namespaceUri) {
	Q_CHECK_PTR(_connection);
	bool result = false;
	QMetaObject::invokeMethod(_connection, "isStreamFeatureSupported", Qt::DirectConnection,
		Q_RETURN_ARG(bool, result), Q_ARG(QString, name), Q_ARG(QString, namespaceUri));

	return result;
}

void XmppClient::processConnectionError(Connection::ConnectionError error) {
	emit connectionError(error);
}

void XmppClient::processConnectionState(Connection::ConnectionState state) {
	emit connectionState(state);
}

void XmppClient::processStreamError(StreamError error) {
	if (_streamErrorListener) {
		if (_streamErrorListener->process(this, &error))
			return;
	}

	emit streamError(error);
}

void XmppClient::processStanzaError(StanzaError error) {
	{
		QMutexLocker locker(&_stanzaErrorListenersLock);
		foreach (StanzaErrorListener* listener, _stanzaErrorListeners) {
			if (listener->process(this, &error))
				return;
		}
	}

	emit stanzaError(error);
}

void XmppClient::processReadyToAuthenticate() {
	emit readyToAuthenticate();
}

void XmppClient::processAuthenticated() {
	 emit authenticated();
}

void XmppClient::processAuthFailure(Connection::AuthFailure failure) {
	emit authFailure(failure);
}

void XmppClient::processResourceBindingError(Connection::ResourceBindingError error) {
	emit resourceBindingError(error);
}

void XmppClient::processResourceBinded(QString jid) {
	emit resourceBinded(jid);	
}


void XmppClient::processSessionEstablishmentError(Connection::SessionEstablishmentError error) {
	emit sessionEstablishmentError(error);
}

void XmppClient::processSessionEstablished() {
	emit sessionEstablished();
}

void XmppClient::processStreamClosed() {
	emit sessionClosed();
}

void XmppClient::processMessageReceived(Message message) {
	{
		QWriteLocker locker(&_messageListenersLock);
		foreach (MessageListener* listener, _messageListeners) {
			if (listener->received(&message)) {
				return;
			}
		}
	}

	searchTaskToProcess(&message);

	emit messageReceived(message);
}

void XmppClient::processPresenceReceived(Presence presence) {
	{
		QWriteLocker locker(&_presenceListenersLock);
		foreach (PresenceListener* listener, _presenceListeners) {
			if (listener->received(&presence)) {
				return;
			}
		}
	}

	searchTaskToProcess(&presence);

	emit presenceReceived(presence);
}

void XmppClient::searchTaskToProcess(Stanza* stanza) {
	// no stanza id
	if (stanza->id().isEmpty()) {
		return;
	}

	{
		QMutexLocker locker(&_tasksLock);
		foreach (StanzaTask* task, _tasks) {
			if (task->matches(this, stanza)) {
				if (task->process(this, stanza)) {
					if (task->autoDelete()) {
						_tasks.removeOne(task);
						delete task;
					}
				}

				break;
			}
		}
	}
}

void XmppClient::processIqReceived(Iq iq) {
	{
		QWriteLocker locker(&_iqListenersLock);
		foreach (IqListener* listener, _iqListeners) {
			if (listener->received(&iq)) {
				return;
			}
		}
	}

	searchTaskToProcess(&iq);

	emit iqReceived(iq);
}

void XmppClient::execute(StanzaTask* task) {
	{
		QMutexLocker locker(&_tasksLock);
		_tasks.append(task);
	}

	task->process(this, 0);
}

void XmppClient::addStanzaListener(StanzaListener* listener) {
	PresenceListener* presenceListener = dynamic_cast<PresenceListener*>(listener);
	if (presenceListener) {
		QWriteLocker locker(&_presenceListenersLock);
		_presenceListeners.append(presenceListener);

		return;
	}

	MessageListener* messageListener = dynamic_cast<MessageListener*>(listener);
	if (messageListener) {
		QWriteLocker locker(&_messageListenersLock);
		_messageListeners.append(messageListener);

		return;
	}

	IqListener* iqListener = dynamic_cast<IqListener*>(listener);
	if (iqListener) {
		QWriteLocker locker(&_iqListenersLock);
		_iqListeners.append(iqListener);
	}
}

void XmppClient::removeStanzaListener(StanzaListener* listener) {
	PresenceListener* presenceListener = dynamic_cast<PresenceListener*>(listener);
	if (presenceListener) {
		QWriteLocker locker(&_presenceListenersLock);
		_presenceListeners.removeOne(presenceListener);

		return;
	}

	MessageListener* messageListener = dynamic_cast<MessageListener*>(listener);
	if (messageListener) {
		QWriteLocker locker(&_messageListenersLock);
		_messageListeners.removeOne(messageListener);

		return;
	}

	IqListener* iqListener = dynamic_cast<IqListener*>(listener);
	if (iqListener) {
		QWriteLocker locker(&_iqListenersLock);
		_iqListeners.removeOne(iqListener);
	}
}

void XmppClient::addStanzaErrorListener(StanzaErrorListener* listener) {
	QMutexLocker locker(&_stanzaErrorListenersLock);
	_stanzaErrorListeners.append(listener);
}

void XmppClient::removeStanzaErrorListener(StanzaErrorListener* listener) {
	QMutexLocker locker(&_stanzaErrorListenersLock);
	_stanzaErrorListeners.removeOne(listener);
}

void XmppClient::setStreamErrorListener(StreamErrorListener* listener) {
	removeStreamErrorListener(listener);

	_streamErrorListener = listener;
}

void XmppClient::removeStreamErrorListener(StreamErrorListener* listener) {
	if (_streamErrorListener) {
		delete _streamErrorListener;
		_streamErrorListener = 0;
	}
}

}
